-- toolset_env_path

local _new_toolset_menu = {
	{ "dialog/config/toolchain/new/local", 0, 0xFF01 };
	{ "dialog/config/toolchain/new/ssh", 0, 0xFF02 };
};

local function collect_toolset_info(toolset)
	return {
		["name"] = toolset.name;
		["remote"] = toolset.remote;
		["type"] = toolset["type"];
		["custom"] = toolset["custom"];
		["version"] = toolset["version"];
		["target"] = toolset.target;
		["wsl"] = toolset.wsl;
		["home"] = toolset.home;
		["bin"] = table.concat(toolset.bin, ";");
		["lib"] = table.concat(toolset.lib, ";");
		["include"] = table.concat(toolset.include, ";");
		["tools"] = table.clone(toolset.tools);
	};
end;


local function collect_current_toolsets(toolset_info)
	if CLANGD_PATHS and CLANGD_PATHS[1] then
		table.insert(toolset_info, {
				["name"] = "clangd";
				["type"] = "clangd";
				["bin"] = CLANGD_PATHS[1];
		});
	end;
	if NINJA_PATHS and NINJA_PATHS[1] then
		table.insert(toolset_info, {
				["name"] = "ninja";
				["type"] = "ninja";
				["bin"] = NINJA_PATHS[1];
		});
	end;
	if CMAKE_PATHS and CMAKE_PATHS[1] then
		table.insert(toolset_info, {
				["name"] = "cmake";
				["type"] = "cmake";
				["bin"] = CMAKE_PATHS[1];
		});
	end;
	if CCACHE_PATHS and CCACHE_PATHS[1] then
		table.insert(toolset_info, {
				["name"] = "ccache";
				["type"] = "ccache";
				["bin"] = CCACHE_PATHS[1];
		});
	end;
	if GDB_PATHS and GDB_PATHS[1] then
		table.insert(toolset_info, {
				["name"] = "gdb";
				["type"] = "gdb";
				["bin"] = GDB_PATHS[1];
		});
	end;

	for idx, toolset in ipairs(TOOLSETS) do
		table.insert(toolset_info, collect_toolset_info(toolset));
	end;
end;

function toolset_manager:on_init()
	self._dissmissed = nil;
	-- print("initialize toolset_manager");
	self.new_toolset_menu.reset();
	make_menu(self.new_toolset_menu, _new_toolset_menu, {"new toolset popup"});
	local toolset_info = {};
	self._toolset_info = toolset_info;
	collect_current_toolsets(toolset_info);
	self.init_toolset(toolset_info);
end;

function toolset_manager:on_new_toolset(t, info)
	local exists_toolsets = {};
	for idx, ti in ipairs(self._toolset_info) do
		exists_toolsets[ti.name] = ti;
	end;

	if t == "local" then
		local toolsets = search_local_toolset(info);
		if toolsets == nil or #toolsets == 0 then
			return -1;
		end;
		local toolset_info = self._toolset_info;
		local new_toolset_info = {};
		for idx, toolset in ipairs(toolsets) do
			local tinfo = collect_toolset_info(toolset);
			tinfo["custom"] = true;
			if not exists_toolsets[tinfo.name] then
				table.insert(toolset_info, tinfo);
				table.insert(new_toolset_info, tinfo);
			elseif exists_toolsets[tinfo.name].home ~= tinfo.home then
				local name = tinfo.name;
				for idx = 1, 99 do
					local new_name = string.format("%s(%d)", name, idx);
					if not exists_toolsets[new_name] then
						tinfo.name = new_name;
						table.insert(toolset_info, tinfo);
						table.insert(new_toolset_info, tinfo);
						break;
					end;
				end;
			end;
		end;
		if #new_toolset_info == 0 then
			return 1;
		end;
		self.add_new_toolset(new_toolset_info);
		return 0;
	end;
	if t == "ssh" then
		local ssh_host = info;
		local cmds = {
			"echo ${HOME}";
			"echo ${LANG}";
			"echo ${PATH}";
			"gdb -version || echo no-gdb";
			"cmake -version || echo no-cmake";
			"ninja --version || echo no-ninja";
			"alias ls=ls && (ls -g --time-style=+%s /usr/local/bin/ /usr/bin/ $PREFIX/bin/ || echo error)";
		};
		local command_info = {};
		local toolset_info = {
		};
		local command_handlers = {
			[1] = function(self, info)
				toolset_info["home"] = string.match(info, "(.-)\n");
			end;
			[2] = function(self, info)
				toolset_info["lang"] = string.match(info, "(.-)\n");
			end;
			[3] = function(self, info)
				toolset_info["path"] = string.match(info, "(.-)\n");
			end;
			[4] = function(self, info)
				toolset_info["gdb"] = string.match(info, "^GNU%s+gdb%s+%(.-%)%s+([%d.]+)");
				if toolset_info["gdb"] then
					print("Found GDB ", toolset_info["gdb"]);
				end;
			end;
			[5] = function(self, info)
				toolset_info["cmake"] = string.match(info, "^cmake%s+version%s+([%d.]+)");
				if toolset_info["cmake"] then
					print("Found CMake ", toolset_info["cmake"]);
				else
					print("\x1b[33;41mMissing CMake!\x1b[0m");
				end;
			end;
			[6] = function(self, info)
				toolset_info["ninja"] = string.match(info, "^%s*([%d.]+)");
				if toolset_info["ninja"] then
					print("Found Ninja-Build ", toolset_info["ninja"]);
				else
					print("\x1b[33;41mMissing Ninja-Build!\x1b[0m");
				end;
			end;
			[7] = function(self, info)
				local current_home_path = "";
				local lists = {};

				for line in string.gmatch(info, "([^\n]*)\n") do
					local line_len = #line;
					if line_len > 0 then
						if line == "error" then
						elseif line:sub(line_len, line_len) == ":" then
							current_home_path = line:match("(.*):");
						elseif line:sub(1, 5) == "total" then
						elseif line:sub(1, 5) == "ls: c" then
						else
							local filename = nil;
							local link = nil;
							if line:find("->") then
								filename, link = line:match("[-%w]+%s+%d+.-%s+%d+%s+%d+%s+(.-)%s+%-%>%s+(.*)");
							else
								filename = line:match("[-%w]+%s+%d+.-%s+%d+%s+%d+%s+(.+)");
							end;
							if current_home_path == "/bin/" then
							elseif filename:match("^c%d+%-gcc$") then
								-- discard c99-gcc, c89-gcc...
							elseif filename:match("^.*g%+%+%-*%d*$") then
								local tool_type = "gcc";
								if filename:find("clang") then
									tool_type = "clang";
								end;
								table.insert(lists, {
										["type"] = tool_type;
										path = current_home_path,
										name = filename,
										["link"] = link,
								});
							elseif filename:match("^.*gcc%-*%d*$") then
								-- is gcc
								table.insert(lists, {
										["type"] = "gcc";
										path = current_home_path,
										name = filename,
										["link"] = link,
								});
							elseif filename:match("^.*clang%+*%-*%d*$") then
								-- is clang
								table.insert(lists, {
										["type"] = "clang";
										path = current_home_path,
										name = filename,
										["link"] = link,
								});
							end;
						end;
					else
						current_home_path = "";
					end;
				end;

				-- merge links
				local target_map = {};
				for idx, info in ipairs(lists) do
					target_map[make_path(info.path, info.name)] = info;
				end;

				for idx = #lists, 1, -1 do
					local info = lists[idx];
					if info.link then
						local link_target = make_path(info.path, info.link);
						local linked_target = target_map[link_target] or link_target[info.link];
						if linked_target then
							local is_cxx = string.find(info.name, "g%+%+") == nil;
							local is_link_cxx = string.find(linked_target.name, "g%+%+") == nil;
							if is_link_cxx == is_cxx then
								table.remove(lists, idx);
							end;
						end;
					end;
				end;

				-- generate new commands
				local compiler_info_cmds = {};
				for idx, info in ipairs(lists) do
					local compiler_path = string.gsub(make_path(info.path, info.name), "\\", "/");
					table.insert(
						compiler_info_cmds,
						compiler_path .. " -E -x c++ - -v < /dev/null || " .. compiler_path .. " -E -x c - -v < /dev/null || echo no-compiler"
					);
					-- add new command handler
					table.insert(self,
						function(cmd_idx, ret_code)
							local collect_include = false;
							local compiler_includes = {};
							for line in string.gmatch(table.concat(command_info), "([^\n]*)\n") do
								if line == "#include <...> search starts here:" then
									collect_include = true;
								elseif line == "End of search list." then
									collect_include = false;
								elseif collect_include then
									local remote_path = line:match("^%s*(.+)");
									remote_path = make_path(remote_path):gsub("\\", "/");
									table.insert(compiler_includes, remote_path);
								elseif not info["version"] and line:sub(1, 11) == "gcc version" then
									info["version"] = line:match("gcc%s+version%s+(.-)%s+%(");
								elseif not info["target"] and line:sub(1, 7) == "Target:" then
									info["target"] = line:match("Target:%s+(.+)");
								else
									if not info["version"] then
										local clang_version = line:match("clang version%s+(%S+)");
										if clang_version then
											info["version"] = clang_version;
										end;
									end;
									if not info["bin_path"] then
										local clang_path = line:match("^%s+\"(/[^\"]+)\"%s+%-cc1");
										local gcc_path = line:match("^%s+(.-/cc1%w*)%s%-");
										if clang_path then
											info["bin_path"] = clang_path;
										end;
										if gcc_path then
											info["bin_path"] = gcc_path;
										end;
									end;
								end;
							end;
							info.includes = compiler_includes;
						end
					);
				end;
				toolset_info.compilers = lists;
				edx:do_build_append_command(compiler_info_cmds);
			end;
		};
		self.__cancelled = nil;
		local tasks = {
			desc = "search toolsets";
			["cmds"] = cmds;
			no_output = true; -- do not write back output
			on_output = function(cmd_idx, output_line)
				if toolset_manager.__cancelled then
					return;
				end;
				table.insert(command_info, output_line);
			end;
			on_command_begin = function(cmd_idx)
				if toolset_manager.__cancelled then
					return;
				end;
				command_info = {};
			end;
			on_command_end = function(cmd_idx, ret_code)
				if toolset_manager.__cancelled then
					return;
				end;
				dbg_call( function()
					local handler = command_handlers[cmd_idx];
					handler(command_handlers, table.concat(command_info));
				end, "error");
			end;
			on_finished = function(success)
				dbg_call(function()
						if toolset_manager.__cancelled then
							return;
						end;
						local compilers = toolset_info.compilers;
						local compiler_map = {};
						local toolsets = {};
						local toolsets_size = 0;
						local compilers_size = 0;

						for idx, c in ipairs(compilers) do
							if c.bin_path then
								compilers_size = compilers_size + 1;
								local tool = compiler_map[c.bin_path];
								local tools;
								if not tool then
									tool = {};
									local ssh_host_name = ssh_host:gsub("ssh://", "");
									compiler_map[c.bin_path] = tool;
									tool["target"] = c.target;
									tool["name"] = string.gsub(string.format("%s(%s-%s-%s)", ssh_host_name, c["type"], c["target"], c["version"]), "[: ]", "_");
									tools = {};
									tool["remote"] = ssh_host;
									tool["home"] = toolset_info["home"];
									tool["tools"] = tools;
									tool["lib"] = {};
									tool["bin"] = {split_string(toolset_info["path"], ":")};
									tool["type"] = c["type"];
									tool["version"] = c["version"];
									tool["include"] = c["includes"];
									tools[c["type"]] = true;
									tool["custom"] = true;
									if c["type"] == "gcc" then
										tools["gcc-pretty-print"] = "/usr/share/gcc/python"
									end;
									if exists_toolsets[tool.name] then
									else
										toolsets_size = toolsets_size + 1;
										toolsets[tool.name] = tool;
									end;

									if not string.find(tool["target"], "mingw") then
										tool["suffixes"] = {
											lib = ".a";
											shared = ".so";
											exec = "";
										};
										if toolset_info["gdb"] then
											tools["gdb"] = "gdb";
											tools["debugger"] = "gdb/mi";
										end;
									end;
									if toolset_info["cmake"] then
										tools["cmake"] = "cmake";
									end;
									if toolset_info["ninja"] then
										tools["ninja"] = "ninja";
									end;
								else
									tools = tool.tools;
								end;

								if string.find(c.name, "clang%+%+") then
									tools["cxx"] = string.gsub(make_path(c.path, c.name), "\\", "/");
									tools["clang++"] = true;
								elseif string.find(c.name, "clang") then
									tools["cc"] = string.gsub(make_path(c.path, c.name), "\\", "/");
									tools["clang"] = true;
								elseif string.find(c.name, "g%+%+") then
									tools["cxx"] = string.gsub(make_path(c.path, c.name), "\\", "/");
									tools["g++"] = true;
								else
									tools["cc"] = string.gsub(make_path(c.path, c.name), "\\", "/");
									tools["gcc"] = true;
								end;
							end;
						end;
						if toolsets_size == 0 then
							if compilers_size == 0 then
								print("\x1b[33;41mNo toolsets was found!\x1b[0m");
								self.__progress_dialog.progress = LANG("dialog/config/toolchain/info/ssh_toolset_not_found");
							else
								print("\x1b[33;41mToolset already exists!\x1b[0m");
								self.__progress_dialog.progress = LANG("dialog/config/toolchain/info/ssh_toolset_exists");
							end;
							self.__progress_dialog.button = LANG("dialog/ok");
							return;
						elseif not toolset_manager._dissmissed then
							local remote_tools = {};
							for key, tool in pairs(toolsets) do
								print("Found toolset ", key);
								local info = collect_toolset_info(tool);
								table.insert(remote_tools, info);
								table.insert(self._toolset_info, info);
							end;
							toolset_manager:add_new_toolset(remote_tools);
						end;

						if toolset_manager.__progress_dialog then
							toolset_manager.__progress_dialog.dismiss();
						end;
				end, "");
			end;
			remote = info;
		};
		edx:do_build(tasks);
		self.__progress_dialog = edx:get_object("dialog:sync_progress_dialog");
		self.__progress_dialog.progress = LANG("dialog/config/toolchain/info/detecting");
		self.__progress_dialog.on_dismissed = function(self)
			toolset_manager.__progress_dialog = nil;
		end;
		self.__progress_dialog.on_cancel = function(self)
			if not toolset_manager.__cancelled then
				toolset_manager.__cancelled = true;
				edx:stop_build();
			end;
		end;
		return 0;
	end;
	return 1;
end;

function toolset_manager:on_dismissed()
	toolset_manager._dissmissed = true;
	-- print("dissmiss toolset_manager");
end;

function toolset_manager:on_apply()
	local toolset_info = self._toolset_info;
	local custom_toolsets = {};
	for idx, info in ipairs(toolset_info) do
		if info.custom then
			local toolinfo = {};
			for name, value in pairs(info) do
				if name == "include" or name == "bin" or name == "lib" then
					toolinfo[name] = {split_string(value, ";")};
				else
					toolinfo[name] = value;
				end;
			end;
			table.insert(custom_toolsets, toolinfo);
		end;
	end;
	update_custom_toolsets(custom_toolsets); -- update TOOLSETS
	menu_bar.update_cmake_toolset(TOOLSETS, nil, function(tool)
			return tool.name, tool.name.."\t"..tool.home;
	end);
	edx:save_toolset();
end;

function toolset_manager:on_download_headers(items)
	dbg_call(function()
			local toolset_info = self._toolset_info;
			local item_map = {};
			for idx, name in ipairs(items) do
				item_map[name] = idx;
			end;
			local remote_cmds = {};
			local ssh_cache_home = edx.make_config_path("ssh-cache", 0);
			for idx = #toolset_info, 1, -1 do
				local info = toolset_info[idx];
				if info.remote and item_map[info.name] then
					local incs = remote_cmds[info.remote];
					if not incs then
						incs = {};
						remote_cmds[info.remote] = incs;
					end;
					for idx, path in ipairs({split_string(info.include, ";")}) do
						incs[path] = path;
					end;
				end;
			end;
			for remote_url, incs in pairs(remote_cmds) do
				local remote_name = string.gsub(remote_url, "ssh://", "");
				remote_name = remote_name:gsub(":", "_");
				local local_home = make_path(ssh_cache_home, remote_name);
				for idx, path in ipairs(table.keys(incs)) do
					local remote_path = path;
					while #remote_path>1 do
						remote_path = make_path(remote_path, ".."):gsub("\\", "/");
						if incs[remote_path] then
							-- parent path exists
							incs[path] = nil;
							break;
						end;
					end;
				end;
				local cmds = {};
				for idx, path in pairs(incs) do
					local cmd = string.format("@DOWNLOAD \"%s\" \"%s\"", path, make_path(local_home, path));
					table.insert(cmds, cmd);
				end;
				remote_cmds[remote_url] = cmds;
			end;
			local progress_dialog = edx:get_object("dialog:sync_progress_dialog");
			progress_dialog.on_cancel = function(self)
				edx:stop_build();
			end;
			
			local function task_enumerator()
				local key, cmds = pairs(remote_cmds)(remote_cmds);
				if key then
					remote_cmds[key] = nil;
					print("downloading...", key);
					progress_dialog.progress = "Downloading...";
					return {
						desc = "search toolsets";
						["cmds"] = cmds;
						["remote"] = key;
						no_output = true; -- do not write back output
						on_output = function(cmd_idx, output_line)
						end;
						on_command_begin = function(cmd_idx)
							local current_cmd = cmds[cmd_idx];
							print("> ", current_cmd);
							local remote_path = current_cmd:match("@DOWNLOAD%s\"(.-)\"");
							progress_dialog.progress = key..remote_path;
						end;
						on_command_end = function(cmd_idx, ret_code)
						end;
						on_finished = function(success)
							local next_task = task_enumerator();
							if next_task then
								edx:do_build(next_task);
							else
								-- no more task
								progress_dialog.dismiss();
							end;
						end;
					};
				else
					print("download finished!");
					return nil;
				end;
			end;
			edx:do_build(task_enumerator());
	end, "");
end;

function toolset_manager:on_remove_toolset(items)
	local toolset_info = self._toolset_info;
	local item_map = {};
	for idx, name in ipairs(items) do
		item_map[name] = idx;
	end;
	for idx = #toolset_info, 1, -1 do
		local info = toolset_info[idx];
		if item_map[info.name] then
			table.remove(toolset_info, idx);
		end;
	end;
end;

function toolset_manager:on_cancel()
	local toolset_info = {};
	self._toolset_info = toolset_info;
	collect_current_toolsets(toolset_info);
	self.init_toolset(toolset_info);
end;
